<template>
  <div class="dv-scroll-board" ref="scrollBoardRef" style="min-height: 0px">
    <div
      class="header"
      v-if="header.length && mergedConfig"
      :style="`background-color: ${mergedConfig.headerBGC};`"
    >
      <div
        class="header-item"
        v-for="(headerItem, i) in header"
        :key="`${headerItem}${i}`"
        :style="` height:
      ${mergedConfig.headerHeight}px; line-height:
      ${mergedConfig.headerHeight}px; width: ${widths[i]}px; `"
        :align="aligns[i]"
        v-html="
          `
      <div
        style='
          padding-top: 20px;
          padding-bottom: 10px;
          font-size: 14px;
          font-family: PingFang SC-Regular, PingFang SC;
          font-weight: 400;
          line-height: 20px;
        '
      >
        ${headerItem}
      </div>
      `
        "
      />
    </div>

    <div
      v-if="mergedConfig"
      class="rows"
      :style="`height: ${
        height - (header.length ? mergedConfig.headerHeight : 0)
      }px;`"
    >
      <div
        class="row-item"
        v-for="(row, ri) in rows"
        :key="`${row.toString()}${row.scroll}`"
        :style="`
            height: ${heights[ri]}px;
            line-height: ${heights[ri]}px;
            background-color: ${
              mergedConfig[row.rowIndex % 2 === 0 ? 'evenRowBGC' : 'oddRowBGC']
            };
          `"
      >
        <div
          class="ceil"
          v-for="(ceil, ci) in row.ceils"
          :key="`${ceil}${ri}${ci}`"
          :style="`width: ${widths[ci]}px;`"
          :align="aligns[ci]"
          :title="ceil"
          v-html="heights[ri] > 0 ? ceil : ''"
          @click="emitEvent('click', ri, ci, row, ceil)"
          @mouseenter="handleHover(true, ri, ci, row, ceil)"
          @mouseleave="handleHover(false)"
        />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { onBeforeUnmount, ref, watch } from "vue";
import { merge, cloneDeep } from "lodash-es";
import useAutoResize from "../../hooks/useAutoResize";

const props = defineProps({
  config: {
    type: Object,
    default: () => ({}),
  },
});
const emits = defineEmits(["click", "mouseover"]);
const scrollBoardRef = ref(null);
interface ScrollBoardConfig {
  header: Array<string>;
  data: Array<any>;
  rowNum: number;
  headerBGC: string;
  oddRowBGC: string;
  evenRowBGC: string;
  waitTime: number;
  headerHeight: number;
  columnWidth: Array<number>;
  align: Array<string>;
  index: Boolean;
  indexHeader: string;
  carousel: string;
  hoverPause: boolean;
}

const defaultConfig = {
  /**
   * @description Board header
   * @type {Array<String>}
   * @default header = []
   * @example header = ['column1', 'column2', 'column3']
   */
  header: [],
  /**
   * @description Board data
   * @type {Array<Array>}
   * @default data = []
   */
  data: [],
  /**
   * @description Row num
   * @type {Number}
   * @default rowNum = 5
   */
  rowNum: 5,
  /**
   * @description Header background color
   * @type {String}
   * @default headerBGC = '#00BAFF'
   */
  headerBGC: "#00BAFF",
  /**
   * @description Odd row background color
   * @type {String}
   * @default oddRowBGC = '#003B51'
   */
  oddRowBGC: "#003B51",
  /**
   * @description Even row background color
   * @type {String}
   * @default evenRowBGC = '#003B51'
   */
  evenRowBGC: "#0A2732",
  /**
   * @description Scroll wait time
   * @type {Number}
   * @default waitTime = 2000
   */
  waitTime: 2000,
  /**
   * @description Header height
   * @type {Number}
   * @default headerHeight = 35
   */
  headerHeight: 60,
  /**
   * @description Column width
   * @type {Array<Number>}
   * @default columnWidth = []
   */
  columnWidth: [],
  /**
   * @description Column align
   * @type {Array<String>}
   * @default align = []
   * @example align = ['left', 'center', 'right']
   */
  align: [],
  /**
   * @description Show index
   * @type {Boolean}
   * @default index = false
   */
  index: false,
  /**
   * @description index Header
   * @type {String}
   * @default indexHeader = '#'
   */
  indexHeader: "#",
  /**
   * @description Carousel type
   * @type {String}
   * @default carousel = 'single'
   * @example carousel = 'single' | 'page'
   */
  carousel: "single",
  /**
   * @description Pause scroll when mouse hovered
   * @type {Boolean}
   * @default hoverPause = true
   * @example hoverPause = true | false
   */
  hoverPause: true,
};
const mergedConfig = ref<ScrollBoardConfig>();
const header = ref<string[]>([]);
const rowsData = ref<any[]>([]);
const rows = ref<any[]>([]);
const widths = ref([]);
const heights = ref<number[]>([]);
const avgHeight = ref(0);
const aligns = ref([]);
const animationIndex = ref(0);
const animationHandler = ref<NodeJS.Timeout>();
const updater = ref(0);
const needCalc = ref(false);

//watcher
watch(
  () => props.config,
  () => {
    // console.log("watcher");
    stopAnimation();
    animationIndex.value = 0;
    calcData();
  },
  { deep: true }
);

//methods
const calcData = () => {
  mergeConfig();

  calcHeaderData();

  calcRowsData();

  calcWidths();

  calcHeights();

  calcAligns();

  animation(true);
};
const handleHover = (enter: any, ri?: any, ci?: any, row?: any, ceil?: any) => {
  if (enter) emitEvent("mouseover", ri, ci, row, ceil);
  if (!(mergedConfig.value as any).hoverPause) return;

  if (enter) {
    stopAnimation();
  } else {
    animation(true);
  }
};
const afterAutoResizeMixinInit = () => {
  calcData();
};
const onResize = () => {
  if (!mergedConfig) return;

  calcWidths();

  calcHeights();
};
const { width, height } = useAutoResize(
  scrollBoardRef,
  onResize,
  afterAutoResizeMixinInit
);

const mergeConfig = () => {
  mergedConfig.value = merge(cloneDeep(defaultConfig), props.config || {});
  // console.log("===mergeConfig===", mergedConfig.value);
};
const calcHeaderData = () => {
  if (!mergedConfig.value?.header?.length) {
    header.value = [];
    // console.log("===calcHeaderData===return");
    return;
  }

  if (mergedConfig.value.index) {
    mergedConfig.value.header.unshift(mergedConfig.value.indexHeader);
  }
  header.value = mergedConfig.value.header;
  // console.log("===calcHeaderData===", mergedConfig.value.header);
};
const calcRowsData = () => {
  //   let { data, index, headerBGC, rowNum } = mergedConfig.value as any;
  let data: Array<any> = [];
  data =
    mergedConfig.value?.data.map((row, i) => {
      row = [...row];

      // const indexTag = `<span class="index" style="background-color: ${
      //   mergedConfig.value?.headerBGC
      // };">${i + 1}</span>`;

      // row.unshift(indexTag);

      return row;
    }) ?? [];

  data = data.map((ceils, i) => ({ ceils, rowIndex: i }));
  // console.log(12, data);
  const rowLength = data.length;

  if (
    rowLength > mergedConfig.value!.rowNum &&
    rowLength < 2 * mergedConfig.value!.rowNum
  ) {
    data = [...data, ...data];
  }
  // console.log(13, data);
  data = data.map((d, i) => ({ ...d, scroll: i }));
  // console.log("===calcRowsData===", data);
  rowsData.value = data;
  rows.value = data;
};

const calcWidths = () => {
  const { columnWidth, header } = mergedConfig.value as any;

  const usedWidth = columnWidth.reduce((all: number, w: number) => all + w, 0);

  let columnNum = 0;
  if (rowsData.value[0]) {
    columnNum = (rowsData.value[0] as any).ceils.length;
  } else if (header.length) {
    columnNum = header.length;
  }

  const avgWidth = (width.value - usedWidth) / (columnNum - columnWidth.length);

  const _widths = new Array(columnNum).fill(avgWidth);

  widths.value = merge(_widths, columnWidth);
  // console.log("===calcWidths===", widths.value);
};
const calcHeights = (onresize = false) => {
  const { headerHeight, rowNum, data } = mergedConfig.value as any;
  let allHeight = height.value;
  // console.log("===calcHeights===", {
  //   headerHeight,
  //   rowNum,
  //   data,
  //   allHeight,
  //   header: header.value,
  // });

  if (header.value.length) {
    // console.log(1);
    allHeight -= headerHeight;
  }

  const _avgHeight = allHeight / rowNum;

  avgHeight.value = _avgHeight;
  // console.log(2, avgHeight.value);
  if (!onresize) {
    heights.value = new Array(data.length).fill(avgHeight.value);
    // console.log(3);
  }
  // console.log("===calcHeights===", heights.value);
};
const calcAligns = () => {
  const columnNum = header.value.length;

  let _aligns = new Array(columnNum).fill("left");

  const { align } = mergedConfig.value as any;

  aligns.value = merge(_aligns, align);
};
const animation = async (start = false) => {
  if (needCalc.value) {
    calcRowsData();
    calcHeights();
    needCalc.value = false;
  }

  let _updater = updater.value;

  const { waitTime, carousel, rowNum } = mergedConfig.value as any;

  const rowLength = rowsData.value.length;

  if (rowNum >= rowLength) return;

  if (start) {
    await new Promise((resolve) => setTimeout(resolve, waitTime));
    if (_updater !== updater.value) return;
  }

  const animationNum = carousel === "single" ? 1 : rowNum;

  let _rows = rowsData.value.slice(animationIndex.value);
  _rows.push(...rowsData.value.slice(0, animationIndex.value));

  rows.value = _rows.slice(0, carousel === "page" ? rowNum * 2 : rowNum + 1);
  heights.value = new Array(rowLength).fill(avgHeight.value);

  await new Promise((resolve) => setTimeout(resolve, 300));
  if (_updater !== updater.value) return;

  heights.value.splice(0, animationNum, ...new Array(animationNum).fill(0));

  animationIndex.value += animationNum;

  const back = animationIndex.value - rowLength;
  if (back >= 0) animationIndex.value = back;

  animationIndex.value = animationIndex.value;
  animationHandler.value = setTimeout(animation, waitTime - 300);
};
const stopAnimation = () => {
  updater.value = (updater.value + 1) % 999999;

  if (!animationHandler.value) return;

  clearTimeout(animationHandler.value);
};
const emitEvent = (type: any, ri: any, ci: any, row: any, ceil: any) => {
  const { ceils, rowIndex } = row;
  // console.log("emitEvent", type, ceil, ceils);
  emits(type, {
    row: ceils,
    ceil,
    rowIndex,
    columnIndex: ci,
  });
};
const updateRows = (rows: any, index: any) => {
  mergedConfig.value = {
    ...(mergedConfig.value as any),
    data: [...rows],
  };

  needCalc.value = true;

  if (typeof index === "number") animationIndex.value = index;
  if (!animationHandler.value) animation(true);
};
onBeforeUnmount(() => {
  stopAnimation();
});
</script>

<style lang="scss">
.dv-scroll-board {
  position: relative;
  width: 100%;
  height: 100%;
  color: #fff;

  .text {
    padding: 0 10px;
    box-sizing: border-box;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .header {
    display: flex;
    flex-direction: row;
    font-size: 15px;

    .header-item {
      transition: all 0.3s;
      font-size: 14px;
      font-family: PingFang SC-Regular, PingFang SC;
      font-weight: 400;
      color: #0fffff;
      line-height: 0px;
    }
  }

  .rows {
    overflow: hidden;

    .row-item {
      display: flex;
      font-size: 14px;
      transition: all 0.3s;
    }

    .ceil {
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }

    .index {
      border-radius: 3px;
      padding: 0px 3px;
    }
  }
}
</style>
